<?php
/**
 * File with shell command for yii framework 1.0.6 
 * 
 * @todo remove html errors ... 
 * @author zadoev@gmail.com
 *
 */


/**
 * Fixtures command class 
 * 
 * This shell command provide load, dump fixtures functionality.
 * Component db should be described in config/console.php  
 * 
 * @author zadoev@gmail.com
 *
 */
class FixturesCommand extends CConsoleCommand
{
	/**
	 * @todo check view row exists
	 */
    public function run($args)
    {
    	Yii::import("application.models.*");
    	Yii::import('application.components.*');
    	
    	if ( ! isset($args[0], $args[1] ) )
    		return $this->usageError('Not enough arguments');
    		
    	$command = $args[0]; 
    	
    	if ( ! in_array( $command, array('dump', 'load') ) ) 
    		return $this->usageError('Unknown command '.$command);
    		
    	$force = isset($args[2]) && $args[2] == 'force' ? true : false; 
   		$this->$command($args[1], $force);
    }
    
    /**
     * Pupulate database with data from xml 
     * 
     * @todo autodected filetype with extension
     * @todo remove parser from here , parser should implement iterator interface
     * @todo create different parsers (yaml, etc...)
     * @param string $file file name with fixtures can be relative path
     * @param bool $force if true all data from model tables will be deleted
     */
    protected function load($file, $force = false )
    {
    	if ( ! file_exists($file) ) 
    		$this->usageError("File not found ".$file);
    		
    	// @todo: test with huge xml, compare with sax parser 
		$xmlIterator = new SimpleXMLIterator(file_get_contents($file));
		
		for( $xmlIterator->rewind(); $xmlIterator->valid(); $xmlIterator->next() ) 
		{
			$current =$xmlIterator->current();
			if ( $current->getName()  != 'model' ) 
				die("tag ".$current->getName()." found instead of model tag");
				
			$modelName =(string)$current['name'];
			$model = new $modelName();
			
			if ( ! $force ) 
			{
				$answer = $this->getUserAnswerForQuestion("Delete all data for model $modelName ? Yes|No ");
				if ( $answer == 'no' )
				{
					echo "Skipping model $modelName\n"; 
					continue;
				}
			}
			 
			echo "Deleting all data for model $modelName: Yes|No\n";
			$model->deleteAll();
			$this->insertRows($xmlIterator, $modelName);
		}
    }
    
    /**
     * Use SimpleXMLElement object for xml node <model> and populate database data with node childs data 
     * 
     * @param SimpleXMLElement $xmlIterator
     * @param string $modelName
     */
	private function insertRows($xmlIterator, $modelName) 
	{
		$counter = 0; 
		$fails = 0; 
		foreach($xmlIterator->getChildren() as $name => $data) 
		{
			if ( $name != 'row'  ) 
				die("could not find a row tag in model");
				
			if ( $this->insertObject($modelName, $data) ) 
				$counter++;
			else 
				$fails++; 
			
		}
		// @todo use translation 
		echo "$counter rows added for $modelName, $fails attempts failed\n";
	}

	/**
	 * Get current xml row node, create model init model data and save into database
	 *  
	 * @param string $modelName
	 * @param SimpleXMLElement $data
	 */
    private function insertObject($modelName, $data) 
    {
    	$object = new $modelName();
    	
		foreach ( $object->getAttributes() as $attribute => $x)
			$object->$attribute = $data->$attribute;
		if ( ! $object->validate() )
		{
			echo "ERR: in row ".$data->asXml().PHP_EOL;
			foreach ( $object->getErrors() as $field => $errors ) 
			{
				foreach ( $errors as $error ) 
					echo "$field - $error".PHP_EOL; 
			}
		}
		  
		return  $object->save();
    }
    
    protected function getUserAnswerForQuestion($msg)
    {
		while ( $answer = strtolower(readline($msg))  ) 
		{
			if ( ! in_array( $answer, array('yes', 'no')) ) 
				continue;
			return $answer; 
		}
    }
    
    /**
     * Dump database data for specified models 
     * 
     * @todo autodect dump type
     * @todo create sql, yaml export 
     * @param string $arg comma separated model names 
     */
    protected function dump( $arg  )
    {
    	$models = split(',', $arg);
    	
		$this->checkModels($models); 
    	
		// direct output for each row to avoid huge memory usage 
    	$this->writeline('<?xml version="1.0" encoding="UTF-8"?>');
    	$this->writeline("<data>");
    	foreach ( $models as $modelName ) 
    	{
    		$model = new $modelName();

    		if ( ! $model->count() )
    			continue; 
    			
			$command=$model->getCommandBuilder()->createFindCommand($model->getTableSchema(), new CDbCriteria());
	    	$dataReader=$command->query();
	    	
	    	$this->writeline('<model name="'.$modelName.'">');
			while(($row=$dataReader->read())!==false) 
			{
				$object = $model->populateRecord($row);
				$this->renderFile(Yii::app()->getBasePath().'/commands/views/fixtures/row.php', array('object' => $object)) ;
				unset($object); 
			}
			$this->writeline('</model>');
			unset($model);
    	}
    	$this->writeline("</data>");
    }
    
    protected function checkModels(array $models)
    {
    	if ( ! $models ) 
    		$this->usageError("dump required comma separated model list, ex: fixtures dump User,Comment .");
    	
    	// additional loop to make check before output start    	
		foreach ( $models as $modelName )
		{    		
			// dont' know how to catch autoload error 
    		if ( ! file_exists(Yii::app()->getBasePath().'/models/'.$modelName.'.php') )
    			$this->usageError("Unknown model : ".$modelName);
		}	
    }
    
    public function getHelp()
    {
        $out = 'Usage: fixtures PathToApplicationConfig command [arg] [force]'."\n\n";
        $out .= "Possible commands\n\n";
        $out .= "\tdump Modelname1,Modelname2,...,ModelnameN: \n\t\tprint xml file with data from specified models\n";
        $out .= "\tload filename [force]: \n\t\treplace data in db with data from from xml file for used models into db \n";
        $out .= "\tfilename required only for load command, should be valid xml file with fixtures data \n";
        $out .= "\t[force] optional argument if specified user promt will be always Yes (danger!) \n";
        return $out; 
    }
    
    /**
     * For next usage (write in file), write message into stdout (now)
     * @todo write into file handler, by defeault php://stdout  
     * @param string $msg
     */
    protected function write($msg) 
    {
    	echo $msg; 
    }
    
    /**
     * Write message as line into stdout
     * 
     * @see write 
     * @param string $msg
     */
    protected function writeline($msg)
    {
    	$this->write($msg.PHP_EOL);
    }
}